﻿using System;
using System.Diagnostics;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Labthink.PCSoft.UniversalHost4.Model;
using Labthink.PCSoft.UniversalHost4.Model.Flow;
using Labthink.PCSoft.UniversalHost4.Utility;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using NLog.Fluent;

namespace Labthink.PCSoft.UniversalHost4.TcpCommands
{
    public class SocketClient
    {
        private TcpClient _client;
        private Socket _socket;
        private readonly string _ip;//TCP服务器
        private readonly int _port;//端口
        private byte[] _readBuffer;
        public int Timeout = 120  * 1000;
        public int ReceiveTimeout = 120 * 1000;
        public int SendTimeout = 120 * 1000;
        public bool IsFileProcess;//文件处理过程中不需要发送心跳
        private readonly string _tempPath = Path.Combine(Path.GetTempPath(), "Labthink");
        private static readonly IsoDateTimeConverter TimeFormat = new IsoDateTimeConverter { DateTimeFormat = "yyyy-MM-dd HH:mm:ss" };
        private readonly object _lock = new object();
        private string _seed;
        private static string SplitStr = "\r\n\n\r";
        public CancellationTokenSource Cts { get; } = new CancellationTokenSource();
        public delegate bool ReceiveData2Event(SocketData sd);

        public ReceiveData2Event R2E;
        #region 已过时，请不要再使用

        [Obsolete("已过时，请不要再使用")]
        public event EventHandler<DataArgs> OnReceiveRealtimeDataHandler;
        [Obsolete("已过时，请不要再使用")]
        public event EventHandler<DataArgs> OnReceiveResultsDataHandler;
        [Obsolete("已过时，请不要再使用")]
        public event EventHandler<DataArgs> OnReceiveParamDataHandler;
        [Obsolete("已过时，请不要再使用")]
        public event EventHandler<TestInfoArgs> OnReceiveAllTestStartHandler;
        [Obsolete("已过时，请不要再使用")]
        public event EventHandler<TestInfoArgs> OnReceiveAllTestStopHandler;
        [Obsolete("已过时，请不要再使用")]
        public event EventHandler<TestInfoArgs> OnReceiveTestStartHandler;

        /// <summary>
        /// 整个试验结束
        /// </summary>
        [Obsolete("已过时，请不要再使用")]
        public event EventHandler<TestInfoArgs> OnReceiveTestStopHandler;
        [Obsolete("已过时，请不要再使用")]
        public event EventHandler<StatusArgs> OnReceiveTestStatusHandler;
        [Obsolete("已过时，请不要再使用")]
        public event EventHandler<AlarmArgs> OnReceiveTestAlarmHandler;
        [Obsolete("已过时，请不要再使用")]
        public event EventHandler<CustomArgs> OnReceiveCustomHandler;
        [Obsolete("已过时，请不要再使用")]
        public event EventHandler<BaseArgs> OnReceiveIsControlHandler;
     
        #endregion

        public SocketClient()
        {

        }

        public SocketClient(string ip, int port)
        {
            _ip = ip;
            _port = port;
            IsFileProcess = false;
        }

        public void SetMixSeed(string mixSeed)
        {
            _seed = mixSeed;
        }

        public bool Connect()
        {
            try
            {
                _client = new TcpClient
                {
                    ReceiveTimeout = ReceiveTimeout,
                    SendTimeout = SendTimeout
                };

                Log.Info().Message("尝试连接到服务器。。。").Write();
                if (!_client.ConnectAsync(_ip, _port).Wait(Timeout))
                {
                    Log.Error().Message($"无法连接到{_ip}:{_port}!").Write();
                    IsConnected = false;
                    return false;
                }

                IsConnected = true;
                _socket = _client.Client;
                _readBuffer = new byte[1024 * 1024];

                Log.Info().Message("已连接到 {0}", _socket.RemoteEndPoint).Write();
                return IsConnected;
            }
            catch (Exception ex)
            {
                Log.Fatal().Message(ex.Message).Write();
                IsConnected = false;
                return IsConnected;
            }
        }

        /// <summary>
        /// 获取或者设置网络连接状态
        /// </summary>
        public bool IsConnected { get; set; }

        public (bool isSuccess, string msg) SendCommand(SocketData data)
        {
            if (!IsConnected)
            {
                return (false, "未连接！");
            }

            try
            {
                var str = JsonConvert.SerializeObject(data, TimeFormat);
                byte[] bytesArray = Encoding.UTF8.GetBytes(str + "\r");
                var networkStream = _client.GetStream();
                {
                    networkStream.Write(bytesArray, 0, bytesArray.Length);
                    Log.Trace().Message($"{data.ActionCode}").Write();

                    return (true, "发送成功");
                }
            }
            catch (Exception ex)
            {
                IsConnected = false;
                return (false, ex.Message);
            }
        }

        public (bool isSuccess, SocketData sd) SendReceive(object obj)
        {
            if (!IsConnected)
            {
                return (false, new SocketData { ErrorCode = -503 });
            }

            string actionCode = "";
            DateTime sendTime = DateTime.MinValue;
            try
            {
                if (obj is SocketData data)
                {
                    actionCode = data.ActionCode;
                    var str = JsonConvert.SerializeObject(data, TimeFormat);
                    byte[] bytesArray = Encoding.UTF8.GetBytes(str + SplitStr);
                    lock (_lock)
                    {
                        var s = _socket;
                        var sw = Stopwatch.StartNew();
                        Log.Trace().Message($"Send command {actionCode}").Write();
                        sendTime = DateTime.Now;

                        s.Send(bytesArray);
                        if (!data.HasResponse)
                        {
                            return (true, null);
                        }

                        _readBuffer = new byte[1024 * 1024 * 10];
                        int numberOfBytesRead = s.Receive(_readBuffer);
                        var response = Encoding.UTF8.GetString(_readBuffer, 0, numberOfBytesRead);

                        int nRetry = 0;
                        while (response.IndexOf(SplitStr) < 0)
                        {
                            Thread.Sleep(100);
                            _readBuffer = new byte[1024 * 1024 * 10];
                            numberOfBytesRead = s.Receive(_readBuffer);
                            response += Encoding.UTF8.GetString(_readBuffer, 0, numberOfBytesRead);

                            if (++nRetry >= 5) break;
                        }

                        var msgarr = System.Text.RegularExpressions.Regex.Split(response, SplitStr);
                        if (msgarr.Length < 1)
                        {
                            Log.Error().Message($"格式错误: {response}").Write();
                            throw new FormatException("格式错误");
                        }

                        // 删除SplitStr
                        response = response.Substring(0, response.Length - SplitStr.Length);
                        Log.Trace().Message($"Send command {actionCode} complete,time used:{sw.Elapsed}: {response}").Write();
                        return (true, JsonConvert.DeserializeObject<SocketData>(response));
                    }
                }

                return (false, new SocketData { ActionCode = "" });
            }
            catch (Exception ex)
            {
                var message =
                    $"Command {actionCode}, send time {sendTime:HH:mm:ss.fff} ({(DateTime.Now - sendTime).Seconds} seconds ago) has failed:";
                Log.Error().Message(message).Exception(ex).Write();
                return (false, new SocketData { ErrorCode = -500, ErrorMsg = ex.Message });
            }
        }

        public async Task<(bool isSuccess, SocketData sd)> SendReceiveAsync(SocketData data)
        {
            return await Task.Run(() => SendReceive(data));
        }

        public void ReceiveFromPort2()
        {
            var so = new StateObject
            {
                WorkSocket = _socket
            };

            _socket.BeginReceive(_readBuffer, 0, _readBuffer.Length, SocketFlags.None, _receiveData, so);
        }

        private void _receiveData(IAsyncResult result)
        {
            try
            {
                if (result.AsyncState is StateObject so)
                {
                    var c = so.WorkSocket;
                    if (c == null) return;
                    int length = c.EndReceive(result);
                    if (length > 0)
                    {
                        if (!string.IsNullOrEmpty(so.LastMsgData))
                        {
                            Log.Trace().Message("上次粘包数据：" + so.LastMsgData).Write();
                        }

                        var msgs = so.LastMsgData + Encoding.UTF8.GetString(_readBuffer, 0, length);
                        Log.Trace().Message("待解析数据：" + msgs).Write();
                        string msg1 = "";
                        int nprocess = 0;
                        var msgarr = System.Text.RegularExpressions.Regex.Split(msgs, SplitStr);
                        try
                        {
                            foreach (var msg in msgarr)
                            {
                                nprocess++;
                                msg1 = msg;

                                if (string.IsNullOrEmpty(msg)) continue;
                                so.LastMsgData = msg;

                                var sd = JsonConvert.DeserializeObject<SocketData>(msg);
                                so.LastMsgData = "";
                                _processData(sd);
                            }
                        }
                        catch (Exception e)
                        {
                            Log.Error().Exception(e).Write();
                            Log.Error().Message($"string to deserialize: **{msg1}**").Write();
                        }
                    }

                    c.BeginReceive(_readBuffer, 0, _readBuffer.Length, SocketFlags.None, _receiveData, so);
                }
            }
            catch (Exception ex)
            {
                Log.Error().Exception(ex).Write();
            }
        }

        private void _processData(SocketData sd)
        {
            var b = R2E?.Invoke(sd);
            if (b.HasValue && b.Value) return;
            try
            {
                DataBar db;
                switch (sd.ActionCode)
                {
                    case "-03":
                        //异常
                        Log.Trace().Message("调用异常处理事件").Write();
                        OnReceiveTestAlarmHandler?.Invoke(this, new AlarmArgs { MsgCode = sd.ActionCode, ReviceTime = sd.ActionTime, Alarm = JsonConvert.DeserializeObject<AlarmPlus>(sd.Value) });
                        break;

                    case "111":
                        //单个试验开始
                        Log.Trace().Message("调用单个试验开始处理事件").Write();
                        OnReceiveTestStartHandler?.Invoke(this, new TestInfoArgs { MsgCode = sd.ActionCode, ReviceTime = sd.ActionTime, Info = JsonConvert.DeserializeObject<TestInfo>(sd.Value) });
                        break;

                    case "112":
                        //单个试验结束
                        Log.Trace().Message("调用单个试验结束处理事件");
                        OnReceiveTestStopHandler?.Invoke(this, new TestInfoArgs { MsgCode = sd.ActionCode, ReviceTime = sd.ActionTime, Info = JsonConvert.DeserializeObject<TestInfo>(sd.Value) });
                        break;

                    case "113":
                        //全部试验开始
                        Log.Trace().Message("调用试验全部开始处理事件").Write();
                        OnReceiveAllTestStartHandler?.Invoke(this, new TestInfoArgs { MsgCode = sd.ActionCode, ReviceTime = sd.ActionTime, Info = JsonConvert.DeserializeObject<TestInfo>(sd.Value) });
                        break;

                    case "114":
                        //全部试验结束
                        Log.Trace().Message("调用试验全部结束处理事件").Write();
                        OnReceiveAllTestStopHandler?.Invoke(this, new TestInfoArgs { MsgCode = sd.ActionCode, ReviceTime = sd.ActionTime, Info = JsonConvert.DeserializeObject<TestInfo>(sd.Value) });
                        break;

                    case "121":
                        //参数设置
                        db = JsonConvert.DeserializeObject<DataBar>(sd.Value);
                        Log.Trace().Message("调用试验参数设置处理事件，并反混淆数据").Write();
                        OnReceiveParamDataHandler?.Invoke(this, new DataArgs { MsgCode = sd.ActionCode, ReviceTime = sd.ActionTime, Datas = db });
                        break;

                    case "131":
                        //实时数据
                        db = JsonConvert.DeserializeObject<DataBar>(sd.Value);
                        Log.Trace().Message("调用试验实时数据处理事件，并反混淆数据").Write();
                        OnReceiveRealtimeDataHandler?.Invoke(this, new DataArgs { MsgCode = sd.ActionCode, ReviceTime = sd.ActionTime, Datas = db });
                        break;

                    case "141":
                        //结果
                        db = JsonConvert.DeserializeObject<DataBar>(sd.Value);
                        Log.Trace().Message("调用试验结果处理事件，并反混淆数据").Write();
                        OnReceiveResultsDataHandler?.Invoke(this, new DataArgs { MsgCode = sd.ActionCode, ReviceTime = sd.ActionTime, Datas = db });
                        break;

                    case "160":
                        //试验状态
                        Log.Trace().Message("调用试验状态处理事件").Write();
                        OnReceiveTestStatusHandler?.Invoke(this, new StatusArgs { MsgCode = sd.ActionCode, ReviceTime = sd.ActionTime, Status = JsonConvert.DeserializeObject<TestStatus>(sd.Value) });
                        break;

                    case "409":
                        //获取控制权限
                        Log.Trace().Message("调用设备控制权限处理事件").Write();
                        OnReceiveIsControlHandler?.Invoke(this, new BaseArgs { MsgCode = sd.ActionCode, ReviceTime = sd.ActionTime });
                        break;

                    default:
                        Log.Trace().Message("调用自定义处理事件").Write();
                        OnReceiveCustomHandler?.Invoke(this, new CustomArgs { MsgCode = sd.ActionCode, ReviceTime = sd.ActionTime, Value = sd.Value });
                        break;
                }
            }
            catch (Exception e)
            {
                Log.Error().Exception(e).Write();
                Log.Error().Message(sd.Value).Write();
            }
        }

        public async Task<string> SendFileAsync(SocketData data, bool isDelete = true)
        {
            return await Task.Run(() => SendFile(data, isDelete));
        }

        public async Task<string> GetFileAsync(SocketData data)
        {
            return await Task.Run(() => GetFile(data));
        }

        public string GetFile(object obj)
        {
            IsFileProcess = true;
            if (!IsConnected)
            {
                throw new InvalidOperationException("未连接！");
            }

            try
            {
                var data = obj as SocketData;
                var str = JsonConvert.SerializeObject(data, TimeFormat);
                byte[] bytesArray = Encoding.UTF8.GetBytes(str + SplitStr);
                lock (_lock)
                {
                    var s = _socket;
                    s.Send(bytesArray);
                    Log.Debug().Message($"询问文件发送方：{str}");
                    if (data != null && !data.HasResponse)
                    {
                        Thread.Sleep(500);
                        return string.Empty;
                    }

                    var buffer = new byte[1024];
                    var count = _socket.Receive(buffer);
                    var fistr = Encoding.UTF8.GetString(buffer, 0, count);
                    Log.Debug().Message($"处理文件发送方是否准备好：{fistr}");
                    if (fistr == "" || fistr.StartsWith("--")) return fistr;
                    var settings = new JsonSerializerSettings
                    {
                        TypeNameHandling = TypeNameHandling.All
                    };

                    var fb = JsonConvert.DeserializeObject<SendFileInfo>(fistr, settings);
                    if (!Directory.Exists(_tempPath))
                    {
                        Directory.CreateDirectory(_tempPath);
                    }

                    Log.Debug().Message("接受文件前准备工作");
                    var fn = Path.Combine(_tempPath, fb.FileName);
                    if (File.Exists(fn))
                    {
                        if (Md5Helper.CreateMd5(fn) == fb.Md5)
                        {
                            _socket.Send(Encoding.UTF8.GetBytes("Exists"));
                            IsFileProcess = false;
                            Log.Debug().Message($"文件名存在且MD5值相同，停止接收文件返回已存在的文件.MD5:{fb.Md5}");
                            Thread.Sleep(500);
                            return fn;
                        }

                        File.Delete(fn);
                        Log.Debug().Message("删除文件名相同MD5不同的文件");
                    }

                    _socket.Send(Encoding.UTF8.GetBytes("OK"));
                    Log.Debug().Message("向发送方发送已就绪消息消息");
                    buffer = new byte[fb.PackSize];
                    using (var writer = new FileStream(fn, FileMode.Create, FileAccess.Write, FileShare.None))
                    {
                        for (int i = 0; i < fb.PackCount; i++)
                        {
                            var received = _socket.Receive(buffer);
                            if (i == fb.PackCount - 1)
                            {
                                Log.Debug().Message($"尾包，获取剩余文件字节：{fb.LastPackSize}");
                                while (received != fb.LastPackSize)
                                {
                                    _socket.Send(Encoding.UTF8.GetBytes("999"));
                                    received = _socket.Receive(buffer);
                                }
                            }
                            else
                            {
                                while (received != fb.PackSize)
                                {
                                    Log.Debug().Message($"当前包【{i}】的大小【{received}】不等于包容量【{fb.PackSize}】,重新获取该包");
                                    _socket.Send(Encoding.UTF8.GetBytes("999"));
                                    received = _socket.Receive(buffer);
                                }
                            }

                            _socket.Send(Encoding.UTF8.GetBytes("0"));

                            writer.Write(buffer, 0, received);
                            writer.Flush();
                        }
                    }

                    Log.Trace().Message("文件{0}接收完毕，接收包数量 : {1}", fb.FileName, fb.PackCount).Write();

                    IsFileProcess = false;
                    if (fb.Md5 == Md5Helper.CreateMd5(fn))
                    {
                        _socket.Send(Encoding.UTF8.GetBytes("0"));
                        Thread.Sleep(500);
                        return fn;
                    }

                    _socket.Send(Encoding.UTF8.GetBytes("-1"));
                }
                Thread.Sleep(500);
                return string.Empty;
            }
            catch (Exception ex)
            {
                Log.Error().Exception(ex).Write();
                IsFileProcess = false;
                _socket.Send(Encoding.UTF8.GetBytes("-1"));
                Thread.Sleep(500);
                return string.Empty;
            }
        }

        public string SendFile(object o, bool isDelete = true)
        {
            var data = o as SocketData;

            // ReSharper disable once PossibleNullReferenceException
            var file = data.Value;
            if (string.IsNullOrEmpty(file))
            {
                Log.Error().Message("没有找到要发送的文件 {0}", file).Write();
                return string.Empty;
            }

            if (file == "" || file.StartsWith("--")) return file;
            var fi = new SendFileInfo
            {
                PackSize = 1024,
                FileName = Path.GetFileName(file),
                FullName = file,
                Md5 = Md5Helper.CreateMd5(file)
            };

            var buffer = new byte[fi.PackSize];
            try
            {
                using (var reader = new FileStream(fi.FullName, FileMode.Open, FileAccess.Read, FileShare.None))
                {
                    fi.FileSize = reader.Length;
                    fi.PackCount = (long)Math.Ceiling(fi.FileSize / (double)fi.PackSize);
                    fi.LastPackSize = (int)(fi.FileSize % fi.PackSize);
                    if (fi.LastPackSize == 0)
                        fi.LastPackSize = fi.PackSize;
                    var sd = new SocketData
                    {
                        ActionCode = data.ActionCode,
                        Value = JsonConvert.SerializeObject(fi)
                    };

                    _socket.Send(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(sd)));
                    var count = _socket.Receive(buffer);
                    string mes = Encoding.UTF8.GetString(buffer, 0, count);
                    if (mes != "OK")
                    {
                        Log.Trace().Message("设备忙").Write();
                        return "--" + mes;
                    }

                    var tc = 0;
                    buffer = new byte[fi.PackSize];
                    var result = new byte[1024];
                    for (int i = 0; i < fi.PackCount; i++)
                    {
                        //从文件流读取数据并填充数据包
                        var rc = reader.Read(buffer, 0, buffer.Length);
                        tc += rc;

                        //发送数据包
                        _socket.Send(buffer, 0, rc, SocketFlags.None);
                        count = _socket.Receive(result);
                        mes = Encoding.Unicode.GetString(result, 0, count);
                        while (mes == "999")
                        {
                            count = _socket.Receive(buffer);
                            mes = Encoding.Unicode.GetString(result, 0, count);
                            Log.Trace().Message($"重发第{i}个数据包").Write();
                        }
                    }

                    Log.Trace().Message($"发送{tc}字节").Write();
                    count = _socket.Receive(buffer);
                    mes = Encoding.UTF8.GetString(buffer, 0, count);

                    return mes;
                }
            }
            catch (Exception ex)
            {
                Log.Error().Exception(ex).Message($"发送文件 {file} 失败").Write();
                return string.Empty;
            }
            finally
            {
                if (isDelete) File.Delete(file);
            }
        }

        public void Close()
        {
            Cts.Cancel();
            _client?.Close();
        }

        ~SocketClient()
        {
            _client?.Close();
        }

        protected virtual void OnOnReceiveTestStopHandler(TestInfoArgs e)
        {
            OnReceiveTestStopHandler?.Invoke(this, e);
        }
    }

    public class StateObject
    {
        public Socket WorkSocket;

        public const int BufferSize = 1024;

        public string LastMsgData;
    }

    public class ReceiveDataEventArgs : EventArgs
    {
        public SocketData Sd;
    }

    public class BaseArgs : EventArgs
    {
        public string MsgCode { get; set; }

        public DateTime ReviceTime { get; set; }

        public BaseArgs()
        {
            ReviceTime = DateTime.Now;
        }
    }

    public class TestInfoArgs : BaseArgs
    {
        public TestInfo Info { get; set; }
    }

    public class StatusArgs : BaseArgs
    {
        public TestStatus Status { get; set; }
    }

    public class AlarmArgs : BaseArgs
    {
        public AlarmPlus Alarm { get; set; }
    }

    public class CustomArgs : BaseArgs
    {
        public string Value { get; set; }
    }

    public class DataArgs : BaseArgs
    {
        public DataBar Datas { get; set; }
    }
}
